package com.geek.gkpush.framework.retry.core;

import com.geek.gkpush.framework.retry.action.RetryMsgUpdateAction;
import com.geek.gkpush.framework.retry.enums.RetryResultEnum;
import com.geek.gkpush.framework.retry.model.RetryDelayModel;
import com.geek.gkpush.framework.retry.model.RetryMsgUpdateModel;
import com.geek.gkpush.framework.retry.model.RetryResult;
import com.geek.gkpush.framework.retry.queue.RetryDelayQueue;
import lombok.extern.slf4j.Slf4j;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;

/**
 * <h1>重试消息的 Runnable 处理任务类。</h1><br/>
 *
 * <h1><br/><a href="https://u.geekbang.org/subject/arch3/100614201">Java 业务架构实战营：https://u.geekbang.org/subject/arch3/100614201</a></h1><br/><h1><a href="https://gitee.com/ylimhhmily/BizArchPracticeCamp">Java 业务架构实战营代码地址：https://gitee.com/ylimhhmily/BizArchPracticeCamp</a></h1><br/><h1><a href="https://u.geekbang.org/subject/java4th/1001148?source=app_share">高级 Java 工程体系课 2023 版：https://u.geekbang.org/subject/java4th/1001148?source=app_share</a></h1><br/><h1><a href="https://time.geekbang.org/column/intro/100312101?tab=catalog">Dubbo 源码剖析与实战：https://time.geekbang.org/column/intro/100312101?tab=catalog</a></h1>
 *
 * @author hmilyylimh
 * ^_^
 * @version 0.0.1
 * ^_^
 * @date 2023/10/3 12:22
 */
@Slf4j
public class RetryMsgRunnable implements Runnable {

    private Map<RetryResultEnum, BiConsumer<RetryResult, RetryDelayModel>> funcMap = new HashMap<>();

    private volatile boolean running;
    private RetryDelayQueue delayQueue;
    private RetryDelayProcessor processor;
    private RetryMsgUpdateAction retryMsgUpdateAction;

    public RetryMsgRunnable(RetryDelayQueue delayQueue,
                            RetryDelayProcessor retryDelayProcessor,
                            RetryMsgUpdateAction retryMsgUpdateAction) {
        this.delayQueue = delayQueue;
        this.processor = retryDelayProcessor;
        this.retryMsgUpdateAction = retryMsgUpdateAction;

        initFuncMap();
    }

    private void initFuncMap() {
        funcMap.put(RetryResultEnum.SUCC, (retryResult, retryDelayModel) -> doProcessSucc(retryResult, retryDelayModel));
        funcMap.put(RetryResultEnum.FAIL, (retryResult, retryDelayModel) -> doProcessFail(retryResult, retryDelayModel));
        funcMap.put(RetryResultEnum.NOT_EXISTS, (retryResult, retryDelayModel) -> doProcessNotExists(retryResult, retryDelayModel));
        funcMap.put(RetryResultEnum.LOCK_FAIL, (retryResult, retryDelayModel) -> doProcessLockFail(retryResult, retryDelayModel));
        funcMap.put(RetryResultEnum.EXCEPTION, (retryResult, retryDelayModel) -> doProcessException(retryResult, retryDelayModel));
    }

    /**
     * <h2>启动延迟队列，可以处理任务了。</h2>
     *
     * @param:
     * @return: void
     */
    public void start() {
        running = true;
    }

    /**
     * <h2>停止延迟队列。</h2>
     *
     * @param:
     * @return: void
     */
    public void stop() {
        running = false;
    }

    @Override
    public void run() {
        while (running) {
            try {
                // 从延迟队列中取出任务
                RetryDelayModel task = delayQueue.take();
                task.setExecStartTime(new Date());

                // 核心处理
                doProcess(task);
            } catch (Exception e) {
                log.error("doProcess error.", e);
            }
        }
    }

    private void doProcess(RetryDelayModel task) {
        // 消费延迟队列的数据
        RetryResult retryResult = processor.consumeDelayQueue(task);

        // 处理延迟队列的结果
        BiConsumer<RetryResult, RetryDelayModel> func = funcMap.get(retryResult.getRetryResultEnum());
        if (func == null) {
            // 如果没有对应的策略实现类的话，那默认当作失败处理
            doProcessFail(retryResult, task);
        } else {
            // 策略实现对应的业务逻辑
            func.accept(retryResult, task);
        }
    }

    private void doProcessSucc(RetryResult retryResult, RetryDelayModel task) {
        retryMsgUpdateAction.update2Succ(task, retryResult.getSrcExecStatus());
    }

    private void doProcessFail(RetryResult retryResult, RetryDelayModel task) {
        RetryMsgUpdateModel model = retryMsgUpdateAction.update2Fail(task, retryResult);
        if (model == null) {
            return;
        }

        RetryDelayModel newModel = task.copy();
        newModel.setExecTimeMillis(model.getNextExecTime().getTime());
        processor.reAdd(newModel);
    }

    private void doProcessNotExists(RetryResult retryResult, RetryDelayModel task) {
        // donothing
    }

    private void doProcessLockFail(RetryResult retryResult, RetryDelayModel task) {
        // donothing
    }

    private void doProcessException(RetryResult retryResult, RetryDelayModel retryDelayModel) {
        // donothing
    }
}